/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 *
 * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
 *
 * Contributor(s): 
 *  Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
 *  
 * If you didn't download this code from the following link, you should check
 * if you aren't using an obsolete version: http://www.isqlviewer.com
 */
package org.isqlviewer.xml;

import java.io.File;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.text.ParseException;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;

import org.isqlviewer.sql.ConnectionProfile;
import org.isqlviewer.sql.JdbcService;
import org.isqlviewer.util.BasicUtilities;
import org.isqlviewer.util.IsqlToolkit;
import org.isqlviewer.util.StringUtilities;
import org.xml.sax.Attributes;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

/**
 * TODO Add ServiceDigester Overview JavaDoc.
 * <p>
 * 
 * @author Mark A. Kobold &lt;mkobold at isqlviewer dot com&gt;
 * @version 1.0
 */
public class ServiceDigester extends DefaultHandler {

    public static final int VERSION_2 = 2;
    public static final int VERSION_3 = 3;

    private static final String V3X_DTD_DECLARATION = "<!DOCTYPE isql-service PUBLIC \"-//iSQL-Viewer.org.//DTD JDBC Service Definition 3.0.0//EN\" \"http://isqlviewer.org/dtd/service-version3.dtd\">";

    public static final String REMOTE_URL = "url";
    public static final String REMOTE_VERSION = "version";
    public static final String REMOTE_SYNC_TYPE = "sync-type";
    public static final String REMOTE_RESOURCES = "local-resources";

    private static final String TAG_ROOT = "isql-service";
    private static final String V2_TAG_AUTH = "authorization";
    private static final String V2_TAG_EXTENDED_PROPERTIES = "extended-properties";
    private static final String V2_APPLICATION_PROPERTIES = "jdbc-properties";
    private static final String V2_TAG_RSRC = "resources";
    private static final String V2_TAG_PROP = "property";
    private static final String V2_TAG_JAR = "jar";
    private static final String V2_TAG_CFG = "config";
    private static final String V2_TAG_REMOTE = "remote-config";
    private static final String V2_ATTRIB_SECURE = "secure";
    private static final String V2_ATTRIB_SCHEMA = "preferred-schema";
    private static final String V2_ATTRIB_NAME = "name";
    private static final String V2_ATTRIB_VALUE = "value";
    private static final String V2_ATTRIB_DRVR = "driver";
    private static final String V2_ATTRIB_USER = "user";
    private static final String V2_ATTRIB_PASS = "password";
    private static final String V2_ATTRIB_HREF = "href";

    private static final String V3_TAG_DESCRIPTION = "description";
    private static final String V3_TAG_DRIVER = "driver";
    private static final String V3_TAG_URL = "url";
    private static final String V3_TAG_AUTHORIZATION = "authorization";
    private static final String V3_TAG_PRINCIPAL = "principal";
    private static final String V3_TAG_CREDENTIALS = "credentials";
    private static final String V3_TAG_CONNECTION_PROFILE = "connection-profile";
    private static final String V3_TAG_QUERY_TIMEOUT = "query-timeout";
    private static final String V3_TAG_LOGIN_TIMEOUT = "login-timeout";
    private static final String V3_TAG_JDBC_TRACING = "jdbc-tracing";
    private static final String V3_TAG_PREFERRED_SCHEMA = "preferred-schema";
    private static final String V3_TAG_PREFERRED_CATALOG = "preferred-catalog";
    private static final String V3_TAG_RESOURCES = "resources";
    private static final String V3_TAG_JAR = "jar";
    private static final String V3_ATTRIB_VERSION = "version";
    private static final String V3_ATTRIB_HREF = "href";
    private static final String V3_ATTRIB_ENCRYPTED = "encrypted";
    // private static final String V3_TAG_CONNECTION_LOGGING = "connection-logging";
    // private static final String V3_TAG_LOG_SIZE = "log-size";
    // private static final String V3_TAG_LOG_HOME = "log-home";
    // private static final String V3_TAG_LOG_PATTERN = "log-pattern";

    private static final String ATTRIB_URL = REMOTE_URL;

    private static final byte[] xorKey = new byte[]{
            (byte) 0x01, (byte) 0x08, (byte) 0x10, (byte) 0x18, (byte) 0x02, (byte) 0x0A, (byte) 0x12, (byte) 0x1A,
            (byte) 0x04, (byte) 0x0C, (byte) 0x14, (byte) 0x1C, (byte) 0x06, (byte) 0x0E, (byte) 0x16, (byte) 0x1E,
            (byte) 0x08, (byte) 0x10, (byte) 0x18, (byte) 0x20, (byte) 0x0A, (byte) 0x12, (byte) 0x1A, (byte) 0x22};

    private JdbcService reference = new JdbcService();
    private ConnectionProfile profile = new ConnectionProfile();
    private Properties driverProperties = new Properties();
    private int documentVersion = VERSION_2;
    private boolean ignoreRemoteTag = false;
    private boolean inDriverProperties = false;
    private boolean inEnvironmentProperties = false;
    private boolean do21XDecryption = false;
    private StringBuffer cdataBuffer = new StringBuffer("");

    public static JdbcService parseService(InputSource source, EntityResolver resolver) throws Exception {

        XMLReader xmlreader = BasicUtilities.createXMLReader(false, true);
        ServiceDigester digester = new ServiceDigester();
        if (resolver != null) {
            xmlreader.setEntityResolver(resolver);
        }
        xmlreader.setContentHandler(digester);
        xmlreader.setErrorHandler(digester);
        xmlreader.parse(source);
        return digester.reference;
    }

    public static void writeService(int version, OutputStream out, JdbcService service) {

        writeService(version, out, service, Charset.forName("UTF-8"));
    }

    public static void writeService(int version, OutputStream out, JdbcService service, Charset cs) {

        OutputStreamWriter osw = new OutputStreamWriter(out, cs);
        PrintWriter pw = new PrintWriter(osw, false);
        pw.print("<?xml version=\"1.0\" encoding=\"");
        pw.print(cs.name());
        pw.println("\" ?>");
        try {
            switch (version) {
                case VERSION_2 :
                    v2printServiceRoot(service, pw);
                    break;
                case VERSION_3 :
                    pw.println(V3X_DTD_DECLARATION);
                    v3printServiceRoot(service, pw);
                    break;
                default :
                    throw new IllegalArgumentException("");
            }
        } finally {
            pw.flush();
            pw.close();
            pw = null;
            osw = null;
        }
    }

    private ServiceDigester() {

    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {

        cdataBuffer.append(new String(ch, start, length).trim());
    }

    @Override
    public void startElement(String ln, String ns, String name, Attributes attr) throws SAXException {

        if (TAG_ROOT.equalsIgnoreCase(name)) {
            String val = attr.getValue(V3_ATTRIB_VERSION);
            if (val == null) {
                documentVersion = VERSION_2;
            } else {
                documentVersion = Integer.parseInt(val);
            }
        }
        switch (documentVersion) {
            case VERSION_2 :
                v2startElement(name, attr);
                break;
            case VERSION_3 :
                v3startElement(name, attr);
                break;
        }
    }

    @Override
    public void endElement(String ln, String ns, String name) {

        String cdata = cdataBuffer.toString();
        cdataBuffer.setLength(0);
        switch (documentVersion) {
            case VERSION_2 :
                break;
            case VERSION_3 :
                v3endElement(name, cdata);
                break;
        }
    }

    @Override
    public void endDocument() throws SAXException {

        reference.setProfile(profile);
    }

    private void v3endElement(String name, String cdata) {

        String tag = name.toLowerCase();
        String interpreted = cdata;
        if (interpreted != null) {
            try {
                interpreted = StringUtilities.substituteVariables(cdata, System.getProperties());
            } catch (ParseException e) {
            }
        }
        if (V3_TAG_DESCRIPTION.equals(tag)) {
            reference.setDescription(interpreted);
        } else if (V3_TAG_DRIVER.equals(tag)) {
            reference.setDriverClass(interpreted);
        } else if (V3_TAG_URL.equals(tag)) {
            reference.setUrl(interpreted);
        } else if (V3_TAG_PRINCIPAL.equals(tag)) {
            reference.setPrincipal(interpreted);
        } else if (V3_TAG_CREDENTIALS.equals(tag)) {
            String credentials = cdata;
            if (profile.isCredentialsSecure()) {
                Key encryptionKey = IsqlToolkit.getEncryptionKey();
                String transformation = encryptionKey.getAlgorithm();
                try {
                    credentials = StringUtilities.decryptText(credentials, encryptionKey, transformation);
                } catch (GeneralSecurityException ignored) {
                }
            }
            reference.setCredentials(credentials);
        } else if (V3_TAG_PREFERRED_CATALOG.equals(tag)) {
            profile.setPreferredCatalog(interpreted);
        } else if (V3_TAG_PREFERRED_SCHEMA.equals(tag)) {
            profile.setPreferredSchema(interpreted);
        } else if (V3_TAG_QUERY_TIMEOUT.equals(tag)) {
            profile.setQueryTimeout(Integer.parseInt(interpreted));
        } else if (V3_TAG_LOGIN_TIMEOUT.equals(tag)) {
            profile.setLoginTimeout(Integer.parseInt(interpreted));
        } else if (V3_TAG_JDBC_TRACING.equals(tag)) {
            profile.setTracingEnabled(Boolean.parseBoolean(interpreted));
        } else if (inDriverProperties && V2_TAG_EXTENDED_PROPERTIES.equals(tag)) {
            reference.setEnvironment(driverProperties);
            driverProperties.clear();
            inDriverProperties = false;
        }
    }

    private void v3startElement(String name, Attributes attr) {

        String tag = name.toLowerCase().trim();
        if (TAG_ROOT.equals(tag)) {
            String value = attr.getValue(V2_ATTRIB_NAME);
            if (value != null) {
                try {
                    value = StringUtilities.substituteVariables(value, System.getProperties());
                } catch (ParseException e) {
                }
            }
            reference.setName(value);
        } else if (V2_TAG_EXTENDED_PROPERTIES.equals(tag)) {
            inDriverProperties = true;
        } else if (V2_TAG_JAR.equals(tag)) {
            String uri = attr.getValue(V2_ATTRIB_HREF);
            if (uri != null && uri.trim().length() >= 1) {
                try {
                    uri = StringUtilities.substituteVariables(uri, System.getProperties());
                } catch (ParseException e) {
                }
                profile.addResource(new File(uri));
            }
        } else if (V3_TAG_AUTHORIZATION.equals(tag)) {
            Boolean isEncrypted = Boolean.valueOf(attr.getValue(V3_ATTRIB_ENCRYPTED));
            profile.setCredentialsPersistent(true);
            profile.setCredentialsSecure(isEncrypted.booleanValue());
        } else if (inDriverProperties && V2_TAG_PROP.equals(tag)) {
            String key = attr.getValue(V2_ATTRIB_NAME);
            String value = attr.getValue(V2_ATTRIB_VALUE);
            if (value == null) {
                return;
            }
            if (key != null && key.trim().length() > 0) {
                driverProperties.setProperty(key, value);
            }
        }
    }

    private void v2startElement(String name, Attributes attr) {

        String tag = name.toLowerCase().trim();
        if (TAG_ROOT.equals(tag)) {
            if (!ignoreRemoteTag) {
                reference.setName(attr.getValue(V2_ATTRIB_NAME));
            }
            try {
                do21XDecryption = Boolean.valueOf(attr.getValue(V2_ATTRIB_SECURE)).booleanValue();
            } catch (Throwable t) {
            }
        } else if (V2_TAG_REMOTE.equals(tag)) {
            // TODO re-enable remote services...
        } else if (V2_APPLICATION_PROPERTIES.equals(tag)) {
            inEnvironmentProperties = true;
        } else if (V2_TAG_EXTENDED_PROPERTIES.equals(tag)) {
            inDriverProperties = true;
        } else if (V2_TAG_CFG.equals(tag)) {
            if (attr.getIndex(V2_ATTRIB_SCHEMA) >= 0) {
                profile.setPreferredSchema(attr.getValue(V2_ATTRIB_SCHEMA));
            }
            reference.setUrl(attr.getValue(ATTRIB_URL));
            reference.setDriverClass(attr.getValue(V2_ATTRIB_DRVR));
        } else if (V2_TAG_AUTH.equals(tag)) {
            profile.setCredentialsPersistent(true);
            String credentials = attr.getValue(V2_ATTRIB_PASS);
            if (credentials != null && credentials.trim().length() >= 1) {
                credentials = StringUtilities.decodeBase64(credentials);
                if (do21XDecryption) {
                    credentials = v2decryptString(credentials);
                }
            }
            reference.setCredentials(credentials);
            reference.setPrincipal(attr.getValue(V2_ATTRIB_USER));
        } else if (V2_TAG_PROP.equals(tag)) {
            String key = attr.getValue(V2_ATTRIB_NAME);
            String val = attr.getValue(V2_ATTRIB_VALUE);
            if (key != null && key.trim().length() > 0) {
                if (inDriverProperties) {
                    driverProperties.setProperty(key, val);
                } else if (inEnvironmentProperties) {
                    if (ConnectionProfile.CONNECTION_KEEPALIVE.equals(key)) {
                        profile.setKeepAlive(Boolean.valueOf(val).booleanValue());
                    } else if (ConnectionProfile.JDBC_ESCAPE_PROC.equals(key)) {
                        profile.setEscapeProcessing(Boolean.valueOf(val).booleanValue());
                    } else if (ConnectionProfile.JDBC_GENERATE_KEYS.equals(key)) {
                        profile.setResultsetKeys(Boolean.valueOf(val).booleanValue());
                    } else if (ConnectionProfile.JDBC_MAX_FIELD_SIZE.equals(key)) {
                        try {
                            profile.setMaxFieldSize(Long.parseLong(val));
                        } catch (NumberFormatException nfe) {

                        }
                    } else if (ConnectionProfile.JDBC_MAX_ROWS.equals(key)) {
                        try {
                            profile.setMaxRowCount(Long.parseLong(val));
                        } catch (NumberFormatException nfe) {

                        }
                    } else if (ConnectionProfile.JDBC_REVERSE_FETCHING.equals(key)) {
                        profile.setReverseFetching(Boolean.valueOf(val).booleanValue());
                    } else if (ConnectionProfile.JDBC_TRACE_ENABLED.equals(key)) {
                        profile.setTracingEnabled(Boolean.valueOf(val).booleanValue());
                    } else if (ConnectionProfile.LOGIN_TIMEOUT.equals(key)) {
                        try {
                            profile.setLoginTimeout(Integer.parseInt(val));
                        } catch (NumberFormatException nfe) {

                        }
                    } else if (ConnectionProfile.MAX_EXCEPTION_CHAIN_LENGTH.equals(key)) {
                        try {
                            profile.setMaxExceptionLength(Integer.parseInt(val));
                        } catch (NumberFormatException nfe) {

                        }
                    } else if (ConnectionProfile.PREFERRED_CATALOG.equals(key)) {
                        profile.setPreferredCatalog(val);
                    } else if (ConnectionProfile.PREFERRED_SCHEMA.equals(key)) {
                        profile.setPreferredSchema(val);
                    } else if (ConnectionProfile.PRINT_STACKTRACES.equals(key)) {
                        profile.setDebuggingEnabled(Boolean.valueOf(val).booleanValue());
                    } else if (ConnectionProfile.QUERY_TIMEOUT.equals(key)) {
                        try {
                            profile.setQueryTimeout(Integer.parseInt(val));
                        } catch (NumberFormatException nfe) {

                        }
                    }
                }
            }
        } else if (V2_TAG_JAR.equals(tag)) {
            String uri = attr.getValue(V2_ATTRIB_HREF);
            if (uri != null && uri.trim().length() >= 1) {
                try {
                    URI fileURI = new URI(new URL(uri).toExternalForm());
                    profile.addResource(new File(fileURI));
                } catch (MalformedURLException error) {
                    // TODO add warning here //
                } catch (URISyntaxException e) {
                    // TODO add warning here //
                }
            }
        }
    }

    private static String v2encryptString(final String text) {

        if (text == null) {
            return "";
        }

        String content = text;
        if (content.length() < xorKey.length) {
            int pad = xorKey.length - content.length();
            byte[] padkey = new byte[pad];
            for (int i = 0; i < pad; i++) {
                padkey[i] = xorKey[i];
            }
            content = content.concat(new String(padkey));
        }

        byte[] pass = content.getBytes();
        byte[] xorStr = new byte[pass.length];
        int j = 0;
        for (int i = 0; i < pass.length; i++) {
            if (j >= xorKey.length) {
                j = 0;
            }
            xorStr[i] = (byte) (xorKey[j++] ^ (pass[i]));
        }
        String str = new String(xorStr);
        return str.trim();
    }

    private static String v2decryptString(String text) {

        if (text == null) {
            return "";
        }

        byte[] bytes = text.getBytes();
        byte[] xorStr = new byte[bytes.length];
        int j = 0;
        for (int i = 0; i < bytes.length; i++) {
            if (j >= xorKey.length)
                j = 0;
            xorStr[i] = (byte) ((bytes[i]) ^ xorKey[j++]);
        }
        String newStr = new String(xorStr);
        newStr.replace('\u0000', ' ');
        return newStr.trim();
    }

    private static void v3printServiceRoot(JdbcService service, PrintWriter ps) {

        ps.print("<");
        ps.print(TAG_ROOT);
        ps.print(" ");
        ps.print(V3_ATTRIB_VERSION);
        ps.print("=\"");
        ps.print(Integer.toString(VERSION_3));
        ps.print("\" ");
        ps.print(V2_ATTRIB_NAME);
        ps.print("=\"");
        ps.print(StringUtilities.encodeXMLEntities(service.getName()));
        ps.println("\" >");
        v3printBasicElements(service, ps);
        v3printProfileElements(service, ps);
        ps.print("</");
        ps.print(TAG_ROOT);
        ps.println(">");
    }

    /**
     * @param service
     * @param ps
     */
    private static void v3printProfileElements(JdbcService service, PrintWriter ps) {

        ConnectionProfile profile = service.getProfile();
        ps.print("\t<");
        ps.print(V3_TAG_CONNECTION_PROFILE);
        ps.println('>');
        ps.print("\t\t<");
        ps.print(V3_TAG_QUERY_TIMEOUT);
        ps.print('>');
        ps.print(Integer.toString(profile.getQueryTimeout()));
        ps.print("</");
        ps.print(V3_TAG_QUERY_TIMEOUT);
        ps.println('>');
        ps.print("\t\t<");
        ps.print(V3_TAG_LOGIN_TIMEOUT);
        ps.print('>');
        ps.print(Integer.toString(profile.getLoginTimeout()));
        ps.print("</");
        ps.print(V3_TAG_LOGIN_TIMEOUT);
        ps.println('>');
        ps.print("\t\t<");
        ps.print(V3_TAG_JDBC_TRACING);
        ps.print('>');
        ps.print(Boolean.toString(profile.isTracingEnabled()));
        ps.print("</");
        ps.print(V3_TAG_JDBC_TRACING);
        ps.println('>');
        ps.print("\t\t<");
        ps.print(V3_TAG_PREFERRED_SCHEMA);
        ps.print('>');
        ps.print(StringUtilities.encodeXMLEntities(profile.getPreferredSchema()));
        ps.print("</");
        ps.print(V3_TAG_PREFERRED_SCHEMA);
        ps.println('>');
        ps.print("\t\t<");
        ps.print(V3_TAG_PREFERRED_CATALOG);
        ps.print('>');
        ps.print(StringUtilities.encodeXMLEntities(profile.getPreferredCatalog()));
        ps.print("</");
        ps.print(V3_TAG_PREFERRED_CATALOG);
        ps.println('>');

        ps.print("\t\t<");
        ps.print(V2_TAG_EXTENDED_PROPERTIES);
        ps.println('>');
        Properties environment = service.getEnvironment();
        Set<Entry<Object, Object>> entries = environment.entrySet();
        for (Entry<Object, Object> entry : entries) {
            String key = entry.getKey().toString();
            String value = entry.getValue().toString();
            ps.print("\t\t\t<");
            ps.print(V2_TAG_PROP);
            ps.print(" ");
            ps.print(V2_ATTRIB_NAME);
            ps.print("=\"");
            ps.print(StringUtilities.encodeXMLEntities(key));
            ps.print("\" ");
            ps.print(V2_ATTRIB_VALUE);
            ps.print("=\"");
            ps.print(StringUtilities.encodeXMLEntities(value));
            ps.println("\" />");
        }
        ps.print("\t\t</");
        ps.print(V2_TAG_EXTENDED_PROPERTIES);
        ps.println('>');

        ps.print("\t\t<");
        ps.print(V3_TAG_RESOURCES);
        ps.println('>');
        Enumeration<File> elements = profile.resourceElements();
        while (elements.hasMoreElements()) {
            File next = elements.nextElement();
            ps.print("\t\t\t<");
            ps.print(V3_TAG_JAR);
            ps.print(" ");
            ps.print(V3_ATTRIB_HREF);
            ps.print("=\"");
            ps.print(StringUtilities.encodeXMLEntities(next.getAbsolutePath()));
            ps.println("\" />");
        }
        ps.print("\t\t</");
        ps.print(V3_TAG_RESOURCES);
        ps.println('>');
        ps.print("\t</");
        ps.print(V3_TAG_CONNECTION_PROFILE);
        ps.println('>');
    }

    private static void v3printBasicElements(JdbcService service, PrintWriter ps) {

        ps.print("\t<");
        ps.print(V3_TAG_DESCRIPTION);
        ps.print('>');
        ps.print(StringUtilities.encodeXMLEntities(service.getDescription()));
        ps.print("</");
        ps.print(V3_TAG_DESCRIPTION);
        ps.println('>');
        ps.print("\t<");
        ps.print(V3_TAG_DRIVER);
        ps.print('>');
        ps.print(StringUtilities.encodeXMLEntities(service.getDriverClass()));
        ps.print("</");
        ps.print(V3_TAG_DRIVER);
        ps.println('>');
        ps.print("\t<");
        ps.print(V3_TAG_URL);
        ps.print('>');
        ps.print(StringUtilities.encodeXMLEntities(service.getUrl()));
        ps.print("</");
        ps.print(V3_TAG_URL);
        ps.println('>');
        ConnectionProfile profile = service.getProfile();
        if (profile.isCredentialsPersistent()) {
            ps.print("\t<");
            ps.print(V3_TAG_AUTHORIZATION);
            ps.print(" ");
            ps.print(V3_ATTRIB_ENCRYPTED);
            ps.print("=\"");
            ps.print(Boolean.toString(profile.isCredentialsSecure()));
            ps.println("\" >");
            ps.print("\t\t<");
            ps.print(V3_TAG_PRINCIPAL);
            ps.print('>');
            ps.print(StringUtilities.encodeXMLEntities(service.getPrincipal()));
            ps.print("</");
            ps.print(V3_TAG_PRINCIPAL);
            ps.println('>');
            ps.print("\t\t<");
            ps.print(V3_TAG_CREDENTIALS);
            ps.print('>');
            String credentials = service.getCredentials();
            if (profile.isCredentialsSecure()) {
                Key encryptionKey = IsqlToolkit.getEncryptionKey();
                String transformation = encryptionKey.getAlgorithm();
                try {
                    credentials = StringUtilities.encryptText(credentials, encryptionKey, transformation);
                } catch (GeneralSecurityException ignored) {
                }
            }
            ps.print(StringUtilities.encodeXMLEntities(credentials));
            ps.print("</");
            ps.print(V3_TAG_CREDENTIALS);
            ps.println('>');
            ps.print("\t</");
            ps.print(V3_TAG_AUTHORIZATION);
            ps.println('>');
        }
    }

    private static void v2printServiceRoot(JdbcService service, PrintWriter ps) {

        ConnectionProfile profile = service.getProfile();
        ps.print("<");
        ps.print(TAG_ROOT);
        ps.print(" ");
        ps.print(V2_ATTRIB_SECURE);
        ps.print("=\"");
        ps.print(profile.isCredentialsSecure());
        ps.print("\" ");
        ps.print(V2_ATTRIB_NAME);
        ps.print("=\"");
        ps.print(StringUtilities.encodeXMLEntities(service.getName()));
        ps.println("\" >");
        v2printAuthorizationTag(service, ps);
        v2printServiceConfig(service, ps);
        ps.print("</");
        ps.print(TAG_ROOT);
        ps.println(">");
    }

    private static void v2printAuthorizationTag(JdbcService service, PrintWriter ps) {

        ConnectionProfile profile = service.getProfile();
        if (profile.isCredentialsPersistent()) {
            ps.print("\t<");
            ps.print(V2_TAG_AUTH);
            ps.print(" ");
            ps.print(V2_ATTRIB_USER);
            ps.print("=\"");
            ps.print(StringUtilities.encodeXMLEntities(service.getPrincipal()));
            ps.print("\" ");
            ps.print(V2_ATTRIB_PASS);
            ps.print("=\"");
            String credentials = service.getCredentials();
            if (credentials != null && credentials.trim().length() >= 1) {
                if (profile.isCredentialsSecure()) {
                    credentials = v2encryptString(credentials);
                }
                credentials = StringUtilities.encodeBase64(credentials);
                ps.print(credentials.trim());
            }
            ps.println("\" />");
        }
    }

    private static void v2printServiceConfig(JdbcService service, PrintWriter ps) {

        ConnectionProfile profile = service.getProfile();
        String s = null;

        ps.print("\t<");
        ps.print(V2_TAG_CFG);
        ps.print(" ");
        s = profile.getPreferredSchema();
        if (s != null && s.trim().length() >= 1) {
            ps.print(V2_ATTRIB_SCHEMA);
            ps.print("=\"");
            ps.print(StringUtilities.encodeXMLEntities(s));
            ps.print("\" ");
        }

        s = service.getDriverClass();
        ps.print(V2_ATTRIB_DRVR);
        ps.print("=\"");
        ps.print(StringUtilities.encodeXMLEntities(s));
        ps.print("\" ");
        s = service.getUrl();
        ps.print(ATTRIB_URL);
        ps.print("=\"");
        ps.print(StringUtilities.encodeXMLEntities(s));
        ps.println("\" >");

        Properties properties = service.getEnvironment();
        v2printProperties(ps, properties, 2, V2_TAG_EXTENDED_PROPERTIES);

        properties = profile.toProperties();
        v2printProperties(ps, properties, 2, V2_APPLICATION_PROPERTIES);

        Enumeration elements = profile.resourceElements();
        if (elements.hasMoreElements()) {
            ps.print("\t\t<");
            ps.print(V2_TAG_RSRC);
            ps.println(">");
            while (elements.hasMoreElements()) {
                URL url = (URL) elements.nextElement();
                if (url == null) {
                    continue;
                }
                ps.print("\t\t\t<");
                ps.print(V2_TAG_JAR);
                ps.print(" ");
                ps.print(V2_ATTRIB_HREF);
                ps.print("=\"");
                ps.print(StringUtilities.encodeXMLEntities(url.toExternalForm()));
                ps.println("\" />");
            }
            ps.print("\t\t</");
            ps.print(V2_TAG_RSRC);
            ps.println(">");
        }
        ps.print("\t</");
        ps.print(V2_TAG_CFG);
        ps.println(">");
    }

    /**
     * @param ps
     * @param extProps
     */
    private static void v2printProperties(PrintWriter ps, Properties properties, int depth, String propertyId) {

        if (properties != null && !properties.isEmpty()) {
            for (int i = 0; i < depth; i++) {
                ps.print('\t');
            }
            ps.print('<');
            ps.print(propertyId);
            ps.println('>');
            Iterator itr = properties.entrySet().iterator();
            while (itr.hasNext()) {
                Map.Entry entry = (Map.Entry) itr.next();
                String key = (String) entry.getKey();
                String val = (String) entry.getValue();
                for (int i = 0; i < depth + 1; i++) {
                    ps.print('\t');
                }
                ps.print('<');
                ps.print(V2_TAG_PROP);
                ps.print(" ");
                ps.print(V2_ATTRIB_NAME);
                ps.print("=\"");
                ps.print(StringUtilities.encodeXMLEntities(key).trim());
                ps.print("\" ");
                ps.print(V2_ATTRIB_VALUE);
                ps.print("=\"");
                ps.print(StringUtilities.encodeXMLEntities(val).trim());
                ps.println("\" />");
            }
            for (int i = 0; i < depth; i++) {
                ps.print('\t');
            }
            ps.print("</");
            ps.print(propertyId);
            ps.println(">");
        }
    }
}
